﻿/*
 * Copyright 2010 Pieter De Rycke
 *
 * This file is part of Frozen Bubble XNA.
 *
 * Frozen Bubble XNA is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2, as published by the Free Software Foundation.
 *
 * Frozen Bubble XNA is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with Frozen Bubble XNA. If not, see 
 * <http://www.gnu.org/licenses/>.
 *
 * Artwork:
 *    Alexis Younes <73lab at free.fr>
 *      (everything but the bubbles)
 *    Amaury Amblard-Ladurantie <amaury at linuxfr.org>
 *      (the bubbles)
 *
 * Soundtrack:
 *    Matthias Le Bidan <matthias.le_bidan at caramail.com>
 *      (the three musics and all the sound effects)
 *
 * Design & Programming:
 *    Guillaume Cottenceau <guillaume.cottenceau at free.fr>
 *      (design and manage the project, whole Perl sourcecode)
 *
 * XNA and Windows Phone 7 port:
 *    Pieter De Rycke
 *
 * Websites:
 *    http://www.frozen-bubble.org/
 *    http://pieterderycke.wordpress.com/
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Media;
using FrozenBubble.Screens;
using Microsoft.Xna.Framework.GamerServices;
using System.IO;
using System.Xml.Linq;
using System.Diagnostics;

namespace FrozenBubble
{
    public class Player : GameScreen, IPlayer
    {
        /* perl */
        private IPlayerDimensions _dimensions;
        public int LeftLimit { get; private set; }
        public int RightLimit { get; private set; }
        public int InitialTopLimit { get; private set; }
        public int TopLimit { get; private set; }
        public int InitialBubbleY { get; private set; }
        public Vector2 CenterPanelPosition { get; private set; }
        public int TimeAppearsNewRoot { get; private set; }
        public Vector2 ScorePosition { get; private set; }

        public bool OddSwap { get; private set; }

        public Vector2 PinguinPosition { get; private set; }
        public Vector2 NextBubblePosition { get; private set; }

        // Resources
        private Texture2D frozenTexture;
        private Texture2D prelightTexture;
        private Texture2D hurryPanel1p;
        private Texture2D[] bubbleTextures;
        private Texture2D[] stickingEffect;

#if WINDOWS_PHONE
        private Texture2D logoTexture;
#endif

        private SpriteFont font;

        private SoundEffect stick;
        private SoundEffect destroyGroup;
        private SoundEffect launch;
        private SoundEffect rebound;
        private SoundEffect newroot;
        private SoundEffect lose;
        private SoundEffect hurry;
        private SoundEffect noh;

        // Game state
        public List<Bubble> StickedBubbles { get; private set; }
        public List<Bubble> ExplodingBubbles { get; private set; }
        public List<Bubble> FallingBubbles { get; private set; }
        public List<Bubble> FrozenBubbles { get; private set; }

        private Bubble launchedBubble;
        private Bubble toBeLaunchedBubble;
        private Bubble nextBubble;

        private int _newRoot;
        private int _newRootPrelight;
        private int _newRootPrelightStep;

        private int _hurry;
        private int _hurryOddness;

        public GameState State { get; private set; }

        private CanonSprite _canon;
        private PinguinSprite _pinguin;
        private CompressorSprite _compressor;

        private Random random;

        private bool _drawLosePanel;
        private bool _drawWinPanel;
        private bool _drawHurryPanel;

        // Level Data
        private LevelSet _levels;
        private int _currentLevelNumber;

        // Game Time
        private TimeSpan _startTime;

        // Input
        private InputState _oldInput;

        long loadTime;

        // Deserialization
        public bool IsDeserialization {get; set;}

        public Player(IPlayerDimensions dimensions, LevelSet levels)
            : this()
        {
            _levels = levels;
            _currentLevelNumber = 0;

            CreateCompressorSprite(0, false);
        }

        public Player()
        {
            random = new Random();

            StickedBubbles = new List<Bubble>();
            ExplodingBubbles = new List<Bubble>();
            FallingBubbles = new List<Bubble>();
            FrozenBubbles = new List<Bubble>();

            State = GameState.Game;

            OddSwap = false;

            // Player positions
            IPlayerDimensions dimensions = new Configuration().PlayerDimensions;
            ConfigurePlayerDimensions(dimensions);

            _canon = new CanonSprite(this);
            Components.Add(_canon);

            _pinguin = new PinguinSprite(this, this);
            Components.Add(_pinguin);
        }

        public override void LoadContent()
        {
#if DEBUG
            long start = DateTime.Now.Ticks;
#endif

            hurryPanel1p = Cache.HurryOnePlayer;
            frozenTexture = Cache.BubbleLose;
            prelightTexture = Cache.BubblePreLight;

#if WINDOWS_PHONE
            logoTexture = Cache.Logo;
#endif

            // load the textures of the balls
            bubbleTextures = Cache.Bubbles;

            // Load the textures for the sticking effect
            stickingEffect = Cache.StickingEffect;

            // Load the font
            font = Game.Content.Load<SpriteFont>("Fonts/font");

            // Load Sound Effects
            stick = Game.Content.Load<SoundEffect>("Audio/stick");
            destroyGroup = Game.Content.Load<SoundEffect>("Audio/destroy_group");
            launch = Game.Content.Load<SoundEffect>("Audio/launch");
            rebound = Game.Content.Load<SoundEffect>("Audio/rebound");
            newroot = Game.Content.Load<SoundEffect>("Audio/newroot_solo");
            lose = Game.Content.Load<SoundEffect>("Audio/lose");
            hurry = Game.Content.Load<SoundEffect>("Audio/hurry");
            noh = Game.Content.Load<SoundEffect>("Audio/noh");

            if (!IsDeserialization && _levels != null)
                LoadNextLevel();
            //else
                //DeserializeCurrentLevel();

            if (Storage.IsSoundEnabled)
            {
                bool playSoundTrack = false;
                // Play Soundtrack
                if (MediaPlayer.State == MediaState.Playing)
                {
                    Guide.BeginShowMessageBox("Stop Media Player",
                        "Media is currently playing, do you want to stop it?", new string[] { "Yes", "No" }, 0, 
                        MessageBoxIcon.None, (ar) => 
                            {
                                int? button = Guide.EndShowMessageBox(ar);
                                playSoundTrack = (button == 0);
                            }, null);
                }
                else
                    playSoundTrack = true;

                if(playSoundTrack)
                    MediaPlayer.Play(Game.SoundTrack);
            }

#if DEBUG
            loadTime = (DateTime.Now.Ticks - start) / 10000;
            Debug.WriteLine("Loadtime player.cs: " + loadTime);
#endif
        }

        public override void ExitScreen()
        {
            if (Storage.IsSoundEnabled)
                MediaPlayer.Stop();

            foreach (GameScreen screen in ScreenManager.Screens)
                ScreenManager.RemoveScreen(screen);
        }

        public override void Serialize(Stream stream)
        {
            XDocument xdoc = new XDocument();
            XElement rootElement = new XElement("GamePlayScreen");

            rootElement.Add(new XAttribute("levelset", "FrozenBubble.levels"));
            rootElement.Add(new XAttribute("currentLevelNumber", _currentLevelNumber));
            rootElement.Add(new XAttribute("oddSwap", OddSwap));
            rootElement.Add(new XAttribute("drawLosePanel", _drawLosePanel));
            rootElement.Add(new XAttribute("drawWinPanel", _drawWinPanel));
            rootElement.Add(new XAttribute("drawHurryPanel", _drawHurryPanel));

            rootElement.Add(new XAttribute("newRoot", _newRoot));
            rootElement.Add(new XAttribute("newRootPrelight", _newRootPrelight));
            rootElement.Add(new XAttribute("newRootPrelightStep", _newRootPrelightStep));

            rootElement.Add(new XAttribute("hurry", _hurry));
            rootElement.Add(new XAttribute("hurryOddness", _hurryOddness));

            rootElement.Add(new XAttribute("startTime", _startTime.Ticks));

            rootElement.Add(new XAttribute("gamestate", State));

            // Save the Sticked bubble collection
            rootElement.Add(SerializeBubbleList(StickedBubbles, "stickedbubbles"));
            rootElement.Add(SerializeBubbleList(ExplodingBubbles, "explodingbubbles"));
            rootElement.Add(SerializeBubbleList(FallingBubbles, "fallingbubbles"));
            rootElement.Add(SerializeBubbleList(FrozenBubbles, "frozenbubbles"));

            rootElement.Add(toBeLaunchedBubble.ToXElement("toBeLaunchedBubble"));
            rootElement.Add(nextBubble.ToXElement("nextBubble"));

            // Save the compressor information
            XElement compressorElement = new XElement("compressor");
            compressorElement.Add(new XAttribute("rootlevel", _compressor.NewRootLevel));
            rootElement.Add(compressorElement);

            xdoc.Add(rootElement);
            xdoc.Save(stream);
        }

        public override void Deserialize(Stream stream)
        {
            XDocument xdoc = XDocument.Load(stream);
            XElement rootElement = xdoc.Element("GamePlayScreen");

            string levelsetFile = rootElement.Attribute("levelset").Value;
            this._levels = new LevelSet(levelsetFile);

            this._currentLevelNumber = int.Parse(rootElement.Attribute("currentLevelNumber").Value);
            this.OddSwap = bool.Parse(rootElement.Attribute("oddSwap").Value);
            this._drawLosePanel = bool.Parse(rootElement.Attribute("drawLosePanel").Value);
            this._drawWinPanel = bool.Parse(rootElement.Attribute("drawWinPanel").Value);
            this._drawHurryPanel = bool.Parse(rootElement.Attribute("drawHurryPanel").Value);

            this._newRoot = int.Parse(rootElement.Attribute("newRoot").Value);
            this._newRootPrelight = int.Parse(rootElement.Attribute("newRootPrelight").Value);
            this._newRootPrelightStep = int.Parse(rootElement.Attribute("newRootPrelightStep").Value);

            this._hurry = int.Parse(rootElement.Attribute("hurry").Value);
            this._hurryOddness = int.Parse(rootElement.Attribute("hurryOddness").Value);

            this._startTime = new TimeSpan(long.Parse(rootElement.Attribute("startTime").Value));

            this.State = (GameState)Enum.Parse(typeof(GameState), rootElement.Attribute("gamestate").Value, true);

            int compressorRootLevel = int.Parse(rootElement.Element("compressor").Attribute("rootlevel").Value);

            CreateCompressorSprite(compressorRootLevel, true);

            DeserializeBubbleList(rootElement.Element("stickedbubbles"), StickedBubbles);
            DeserializeBubbleList(rootElement.Element("explodingbubbles"), ExplodingBubbles);
            DeserializeBubbleList(rootElement.Element("fallingbubbles"), FallingBubbles);
            DeserializeBubbleList(rootElement.Element("frozenbubbles"), FrozenBubbles);

            toBeLaunchedBubble = DeserializeBubble((from b in rootElement.Elements("bubble")
                                                    let name = b.Attribute("name")
                                                    where name != null && name.Value == "toBeLaunchedBubble"
                                                    select b).First());

            nextBubble = DeserializeBubble((from b in rootElement.Elements("bubble")
                                            let name = b.Attribute("name")
                                            where name != null && name.Value == "nextBubble"
                                            select b).First());

            IsDeserialization = true;

            DeserializeCurrentLevel();
        }

        public override void Draw(GameTime gameTime)
        {
            // Draw compressor
            foreach (Vector2 extPos in _compressor.Extensions)
                SpriteBatch.Draw(_compressor.ExtensionTexture, extPos, Color.White);
            SpriteBatch.Draw(_compressor.CompressorTexture, _compressor.Position, Color.White);

#if WINDOWS_PHONE
            // Draw logo
            SpriteBatch.Draw(logoTexture, _dimensions.LogoPosition, Color.White);
#endif

            // Level number
            string level = "" + (_currentLevelNumber + 1);
            SpriteBatch.DrawString(font, level, ScorePosition, Color.White);

            // bubbles
            if(launchedBubble != null)
                SpriteBatch.Draw(bubbleTextures[launchedBubble.Number], launchedBubble.ScreenPosition, Color.White);

            foreach (Bubble bubble in StickedBubbles)
            {
                SpriteBatch.Draw(bubbleTextures[bubble.Number], bubble.ScreenPosition, Color.White);

                if (_newRootPrelight > 0)
                {
                    if (bubble.ArrayPosition.X == _newRootPrelightStep)
                        SpriteBatch.Draw(prelightTexture, bubble.ScreenPosition, Color.White);
                }

                if (bubble.StickingStep >= 0 && bubble.StickingStep < 8)
                {
                    SpriteBatch.Draw(stickingEffect[bubble.StickingStep], bubble.ScreenPosition, Color.White);

                    if (bubble.StickingSlowdown)
                    {
                        bubble.StickingSlowdown = false;
                    }
                    else
                    {
                        bubble.StickingStep++;
                        bubble.StickingSlowdown = true;
                    }
                }
            }

            foreach (Bubble bubble in FrozenBubbles)
            {
                SpriteBatch.Draw(bubbleTextures[bubble.Number], bubble.ScreenPosition, Color.White);
                SpriteBatch.Draw(frozenTexture, bubble.ScreenPosition, Color.White);
            }

            if (toBeLaunchedBubble != null)
                SpriteBatch.Draw(bubbleTextures[toBeLaunchedBubble.Number], toBeLaunchedBubble.ScreenPosition, Color.White);

            if(nextBubble != null)
                SpriteBatch.Draw(bubbleTextures[nextBubble.Number], nextBubble.ScreenPosition, Color.White);

            // canon
#if WINDOWS_PHONE
            Vector2 canonPostion = new Vector2((LeftLimit + RightLimit)/2 - 50 * 4.0f/3.0f + _canon.OffsetX, 
                InitialBubbleY + (16 - 50) *4.0f/3.0f + _canon.OffsetY);
#else
            Vector2 canonPostion = new Vector2((LeftLimit + RightLimit) / 2 - 50 + _canon.OffsetX,
                InitialBubbleY + 16 - 50 + _canon.OffsetY);
#endif
            SpriteBatch.Draw(_canon.Texture, canonPostion, Color.White);

            // pinguin
            SpriteBatch.Draw(_pinguin.Texture, PinguinPosition, Color.White);

            // Moving bubbles --> I want them on top of the rest
            foreach (Bubble bubble in ExplodingBubbles)
                SpriteBatch.Draw(bubbleTextures[bubble.Number], bubble.ScreenPosition, Color.White);
            foreach (Bubble bubble in FallingBubbles)
                SpriteBatch.Draw(bubbleTextures[bubble.Number], bubble.ScreenPosition, Color.White);

            if (State == GameState.Game && _drawHurryPanel)
            { 
                SpriteBatch.Draw(hurryPanel1p, _dimensions.HurryPosition, Color.White);
            }
        }

        public override void Update(GameTime gameTime, bool otherScreenHasFocus,
                                                       bool coveredByOtherScreen)
        {
            InputState input = Input.State;

            if (_startTime == TimeSpan.Zero)
                _startTime = gameTime.TotalGameTime;

            if (!otherScreenHasFocus)
            {
                if (!input.IsActionActive(InputAction.Pause) &&
                    _oldInput.IsActionActive(InputAction.Pause))
                {
                    // Set Game in Pause
                    if (Storage.IsSoundEnabled)
                        MediaPlayer.Pause();
                    PauseScreen pauseScreen = new PauseScreen(_dimensions.PauseClipPosition);
                    pauseScreen.Closed += new EventHandler(pauseScreen_Closed);
                    ScreenManager.AddScreen(pauseScreen);
                    IsActive = false;
                }

                if (!input.IsActionActive(InputAction.End) && _oldInput.IsActionActive(InputAction.End))
                {
                    TimeSpan playtime = gameTime.TotalGameTime - _startTime;
                    int playTime = playtime.Seconds + playtime.Minutes * 60;

                    if (ScreenManager.HighScoreManager.IsHighScore(_currentLevelNumber, playTime))
                    {
                        InputBoxScreen.ShowInputBoxScreen("Congratulations!",
                                  "You have a highscore!\nPlease enter your name:",
                                  "",
                                  (userName) =>
                                  {
                                      SaveHighScore(userName, playTime);
                                      ExitScreen();
                                      ShowMainMenu();
                                  });
                    }
                    else
                    {
                        // Exit the current game
                        ExitScreen();
                        ShowMainMenu();
                    }
                }
            }

            if (State == GameState.Game)
            {
                // Handle Hurry
                _hurry++;
                if (_hurry > _dimensions.TimeHurryWarn)
                {
                    int oddness = BubbleConstants.Odd((_hurry - _dimensions.TimeHurryWarn) / (500 / BubbleConstants.TARGET_ANIM_SPEED) + 1);
                    if (BubbleConstants.ToBool(_hurryOddness ^ oddness))
                    {
                        if (BubbleConstants.ToBool(oddness))
                        {
                            PlaySoundEffect(hurry);
                            _drawHurryPanel = true;
                        }
                        else
                        {
                            _drawHurryPanel = false;
                        }
                    }
                    _hurryOddness = oddness;
                }

                if (!otherScreenHasFocus)
                {
                    // Handle Input
                    if ((input.IsActionActive(InputAction.Fire) || _hurry == _dimensions.TimeHurryMax)
                        && launchedBubble == null && toBeLaunchedBubble != null) //TODO: dit klopt nog niet compleet met de perl code
                    {
                        PlaySoundEffect(launch);
                        launchedBubble = toBeLaunchedBubble;
                        launchedBubble.Direction = _canon.Angle;
                        toBeLaunchedBubble = null;
                        _hurry = 0;
                        _drawHurryPanel = false;
                    }
                }

                // Handle Prelight of Bubbles
                if (_newRootPrelight > 0)
                {
                    if (_newRootPrelightStep++ > 30 * _newRootPrelight)
                        _newRootPrelightStep = 0;
                }

                if (launchedBubble != null)
                {
                    // save coordinates for potential collision
                    Vector2 oldScreenPosition = launchedBubble.ScreenPosition;

                    Vector2 screenPosition = launchedBubble.ScreenPosition;
                    screenPosition.X += (float)(BubbleConstants.BUBBLE_SPEED * Math.Cos(launchedBubble.Direction));
                    screenPosition.Y -= (float)(BubbleConstants.BUBBLE_SPEED * Math.Sin(launchedBubble.Direction));
                    launchedBubble.ScreenPosition = screenPosition;

                    if (launchedBubble.ScreenPosition.X < LeftLimit)
                    {
                        PlaySoundEffect(rebound);

                        screenPosition.X = 2 * LeftLimit - screenPosition.X;
                        launchedBubble.ScreenPosition = screenPosition;
                        launchedBubble.Direction -= 2 * (launchedBubble.Direction - Math.PI / 2);
                    }

                    if (launchedBubble.ScreenPosition.X > RightLimit - BubbleConstants.BUBBLE_SIZE)
                    {
                        PlaySoundEffect(rebound);

                        screenPosition.X = (float)(2 * (RightLimit - BubbleConstants.BUBBLE_SIZE) - screenPosition.X);
                        launchedBubble.ScreenPosition = screenPosition;
                        launchedBubble.Direction += 2 * (Math.PI / 2 - launchedBubble.Direction);
                    }

                    if (launchedBubble.ScreenPosition.Y <= TopLimit + _compressor.NewRootLevel * BubbleConstants.ROW_SIZE)
                    {
                        launchedBubble.FindClosestArrayPosition();
                        StickBubble(launchedBubble, true);
                        launchedBubble = null;
                    }
                    else
                    {
                        foreach (Bubble bubble in StickedBubbles)
                        {
                            if (launchedBubble.IsCollision(bubble))
                            {
                                launchedBubble.FindClosestArrayPosition((oldScreenPosition.X + launchedBubble.ScreenPosition.X) / 2,
                                    (oldScreenPosition.Y + launchedBubble.ScreenPosition.Y) / 2);
                                StickBubble(launchedBubble, true);
                                launchedBubble = null;
                                break;
                            }
                        }
                    }
                }

                if (toBeLaunchedBubble == null)
                    GenerateNewBubble();

                VerifyIfEnd();
            }
            else if (State == GameState.Lost)
            {
                if (StickedBubbles.Count > 0)
                {
                    Bubble b = StickedBubbles[StickedBubbles.Count - 1];
                    StickedBubbles.RemoveAt(StickedBubbles.Count - 1);
                    FrozenBubbles.Add(b);
                }
                else if(!_drawLosePanel)    // Only enter once
                {
                    _drawLosePanel = true;

                    int time = GetTotalPlayTime(gameTime.TotalGameTime);
                    ShowLoseScreen(time);

                    PlaySoundEffect(noh);
                }
            }
            else if (State == GameState.Won && !_drawWinPanel)
            {
                if (ExplodingBubbles.Count == 0)
                {
                    _drawWinPanel = true;
                    WinPanelScreen winPanel = new WinPanelScreen(CenterPanelPosition);
                    winPanel.Closed += new EventHandler((sender, e) => 
                    {
                        if (_currentLevelNumber < (_levels.Levels.Count - 1))
                        {
                            _currentLevelNumber++;
                            LoadNextLevel();
                            State = GameState.Game;
                        }
                    });
                    ScreenManager.AddScreen(winPanel);
                }
            }

            // Things that need to be updated in all states of the game

            // Update falling bubbles
            List<Bubble> fallingEnd = new List<Bubble>();
            foreach (Bubble b in FallingBubbles)
            {
                if (b.WaitFall > 0)
                    b.WaitFall--;
                else
                {
                    Vector2 pos = b.ScreenPosition;

                    if (false)
                    {
                        // Not yet implemented
                    }
                    else
                    {
                        b.ScreenPosition = new Vector2(pos.X, pos.Y + b.Speed);
                        b.Speed += (float)BubbleConstants.FREE_FALL_CONSTANT;
                    }
                }

                if (b.ScreenPosition.Y > _dimensions.BottomLimit && b.ChainDestx == 0)
                    fallingEnd.Add(b);
            }

            foreach(Bubble b in fallingEnd)
                FallingBubbles.Remove(b);

            // Update exploding bubbles
            foreach(Bubble bubble in ExplodingBubbles)
            {
                Vector2 pos = bubble.ScreenPosition;
                bubble.ScreenPosition = new Vector2(pos.X + bubble.SpeedX, pos.Y + bubble.SpeedY);
                bubble.SpeedY += (float)BubbleConstants.FREE_FALL_CONSTANT;
            }

            ExplodingBubbles.RemoveAll((b) => b.ScreenPosition.Y > _dimensions.BottomLimit);

            _oldInput = input;
        }

        private void ConfigurePlayerDimensions(IPlayerDimensions dimensions)
        {
            _dimensions = dimensions;

            LeftLimit = _dimensions.LeftLimit;
            RightLimit = _dimensions.RightLimit;
            InitialTopLimit = _dimensions.InitialTopLimit;
            InitialBubbleY = _dimensions.InitialBubbleY;
            CenterPanelPosition = _dimensions.CenterPanelPosition;
            PinguinPosition = _dimensions.PinguinPosition;
            NextBubblePosition = _dimensions.NextBubblePosition;

            TopLimit = InitialTopLimit; // voor 2p is dit niet zo
            TimeAppearsNewRoot = 8;
        }

        private void CreateCompressorSprite(int compressorRootLevel, bool loadContent)
        {
            _compressor = new CompressorSprite(this, _dimensions, compressorRootLevel);
            if (loadContent)
                _compressor.LoadContent();
            Components.Add(_compressor);
        }

        private void LoadNextLevel()
        {
            _compressor.Reset();

            StickedBubbles.Clear();
            ExplodingBubbles.Clear();
            FallingBubbles.Clear();
            FrozenBubbles.Clear();
            toBeLaunchedBubble = null;
            nextBubble = null;
            _drawLosePanel = false;
            _drawWinPanel = false;
            _drawHurryPanel = false;
            _newRoot = 0;
            _newRootPrelight = 0;
            OddSwap = false;
            _hurry = 0;

            Level currentLevel = _levels.Levels[_currentLevelNumber];

            //load the balls
            foreach (BubbleInformation info in currentLevel.Data)
            {
                Vector2 bubblePosition = CalcRealPosGivenArraypos(info.cx, info.cy);
                Bubble bubble = new Bubble(this, info.cx, info.cy,
                    bubblePosition, info.imageNumber);
                RealStickBubble(bubble);
            }

            ScorePosition = CalculateScoreScreenPosition();

            _canon.Reset();
            _pinguin.Reset();
        }

        private void DeserializeCurrentLevel()
        {
            switch(State)
            {
                case GameState.Game:
                    foreach (Bubble b in StickedBubbles)
                        b.FindNeighbours();

                    ScorePosition = CalculateScoreScreenPosition();

                    _canon.Reset();
                    _pinguin.Reset();
                    break;
                case GameState.Won:
                    // Load the next level
                    _currentLevelNumber++;
                    LoadNextLevel();
                    State = GameState.Game;
                    break;
                //case GameState.Lost:
                //    throw new Exception("Invalid state"); //this will force to reload the game; dirty I know ...
            }
        }

        private void pauseScreen_Closed(object sender, EventArgs e)
        {
            IsActive = true;
            if (Storage.IsSoundEnabled)
                MediaPlayer.Resume();
        }

        private void ShowLoseScreen(int time)
        {
            GameScreen loseScreen = new LosePanelScreen(CenterPanelPosition, _currentLevelNumber, time);
            loseScreen.Closed += new EventHandler(
                (o, e) => 
                    {
                        ExitScreen();
                        ShowMainMenu();
                    }
                );
            ScreenManager.AddScreen(loseScreen);
        }

        private void ShowMainMenu()
        {
            GameScreen screen = ScreenFactory.GetScreen(ScreenType.MainMenu);
            ScreenManager.AddScreen(screen);
        }

        private void SaveHighScore(string userName, int playTime)
        {
            if (userName != null)
            {
                Storage storage = ScreenManager.Storage;
                storage.HighScores.AddHighScore(userName, _currentLevelNumber + 1, playTime, false);
                storage.Save();
            }
        }

        /// <summary>
        /// real_stick_bubble
        /// </summary>
        /// <param name="bubble"></param>
        private void RealStickBubble(Bubble bubble)
        {
            foreach (Bubble stickedBubble in StickedBubbles)
            {
                if (stickedBubble.NextTo(bubble))
                {
                    stickedBubble.NeighBours.Add(bubble);
                    bubble.NeighBours.Add(stickedBubble);
                } 
            }

            bubble.ScreenPosition = CalcRealPos(bubble);
            StickedBubbles.Add(bubble);

            if (bubble.ArrayPosition.Y == _compressor.NewRootLevel)
            {
                bubble.IsRootBubble = true;
            }
        }

        private Vector2 CalcRealPos(Bubble bubble)
        {
            return CalcRealPosGivenArraypos(bubble.ArrayPosition.X, bubble.ArrayPosition.Y);
        }

        private Vector2 CalcRealPosGivenArraypos(int cx, int cy)
        {
            float x = (float)(LeftLimit + cx * BubbleConstants.BUBBLE_SIZE +
                BubbleConstants.Odd(cy + BubbleConstants.ToInt(OddSwap)) * BubbleConstants.BUBBLE_SIZE / 2);
            float y = (float)(_dimensions.InitialTopLimit + cy * BubbleConstants.ROW_SIZE);

            return new Vector2(x, y);
        }

        private void GenerateNewBubble()
        {
            float x;
            float y;
            Vector2 position;

            //next bubble becomes to be launched bubble
            if (nextBubble != null)
            {
                toBeLaunchedBubble = nextBubble;
                x = (float)((LeftLimit + RightLimit) / 2 - BubbleConstants.BUBBLE_SIZE / 2);
                y = InitialBubbleY;
                position = new Vector2(x, y);
                toBeLaunchedBubble.ScreenPosition = position;
            }
            
            //create next bubble
            int number = GenerateBubbleNumber();
            position = NextBubblePosition;
            position.X += LeftLimit;

            nextBubble = new Bubble(this, -1, -1, position, number);
        }

        private int GenerateBubbleNumber()
        {
            List<int> colorsLeft = new List<int>((from c in StickedBubbles select c.Number).Distinct());

            int number = random.Next(colorsLeft.Count);
            return colorsLeft[number];
        }

        private void StickBubble(Bubble bubble, bool countForRoot)
        {
            HashSet<Bubble> bubbleGroup = bubble.FindBubbelGroup();

			if(bubbleGroup.Count < 3)
			{
                PlaySoundEffect(stick);
                bubble.StickingStep = 0; // Prepare for sticking annimation
            	RealStickBubble(bubble);
			}
			else
			{
                PlaySoundEffect(destroyGroup);
                // Neighbours updaten van bubbles grenzend aan diegene die we willen verwijderen
                foreach (Bubble b in bubbleGroup)
                    foreach(Bubble neighbour in b.NeighBours)
                        neighbour.NeighBours = new List<Bubble>(neighbour.NeighBours.Except(bubbleGroup));

                StickedBubbles = new List<Bubble>(StickedBubbles.Except(bubbleGroup));

                DestroyBubbles(bubbleGroup);

                // Find falling bubbles
                foreach (Bubble b in StickedBubbles)
                    b.Mark = 0;
                List<Bubble> stillSticked = new List<Bubble>();
                List<Bubble> neighbours = new List<Bubble>(from b in StickedBubbles
                                                           where b.IsRootBubble
                                                           select b);
                int distanceToRoot = 0;
                while (true)
                {
                    distanceToRoot++;

                    foreach (Bubble b in neighbours)
                    {
                        b.Mark = distanceToRoot;
                        stillSticked.Add(b);
                    }

                    List<Bubble> newNeighbours = new List<Bubble>();
                    foreach (Bubble b in neighbours)
                        foreach (Bubble next in b.NeighBours)
                            if (next.Mark == 0)
                                newNeighbours.Add(next);
                    neighbours = newNeighbours;

                    if (neighbours.Count == 0)
                        break;
                }
                List<Bubble> falling = new List<Bubble>(StickedBubbles.Except(stillSticked));
                foreach (Bubble b in falling)
                    StickedBubbles.Remove(b);

                // Prepare falling bubbles
                if (falling.Count > 0)
                {
                    int maxCyFalling = falling.Max((f) => f.ArrayPosition.Y);
                    int shiftOnSameLine = 0;
                    int line = maxCyFalling;
                    foreach (Bubble b in (from f in falling
                                          orderby f.ArrayPosition.Y, f.ArrayPosition.X
                                          select f)) // Sort bottom-to-up / right-to-left
                    {
                        if (line != b.ArrayPosition.Y)
                            shiftOnSameLine = 0;
                        line = b.ArrayPosition.Y;
                        b.WaitFall = (maxCyFalling - b.ArrayPosition.Y) * 5 + shiftOnSameLine;
                        shiftOnSameLine++;
                        b.Speed = 0;
                        FallingBubbles.Add(b);
                    }
                }
			}

            if (countForRoot)
            {
                _newRoot++;
                if (_newRoot == TimeAppearsNewRoot - 1)
                {
                    _newRootPrelight = 2;
                    _newRootPrelightStep = 0;
                }
                else if (_newRoot == TimeAppearsNewRoot)
                {
                    _newRootPrelight = 1;
                    _newRootPrelightStep = 0;
                }
                else if (_newRoot > TimeAppearsNewRoot)
                {
                    _newRootPrelight = 0;

                    PlaySoundEffect(newroot);

                    _newRoot = 0;
                    _compressor.Lower();
                    OddSwap = !OddSwap;

                    foreach (Bubble b in StickedBubbles)
                    {
                        b.ArrayPosition = new Position(b.ArrayPosition.X, b.ArrayPosition.Y + 1);
                        b.ScreenPosition = CalcRealPos(b);
                    }
                }
            }
        }

		private void DestroyBubbles(IEnumerable<Bubble> bubbles)
		{
			foreach(Bubble bubble in bubbles)
			{
                bubble.SpeedX = (float)(random.NextDouble() * 3 - 1.5);
                bubble.SpeedY = (float)(-random.NextDouble() * 4 - 2);
                ExplodingBubbles.Add(bubble);
			}
		}

        private void VerifyIfEnd()
        {
            if (StickedBubbles.Count == 0)
            {
                State = GameState.Won;
            }
            else
            {
                foreach (Bubble b in StickedBubbles)
                {
                    if (b.ArrayPosition.Y > 11)
                    {
                        State = GameState.Lost;
                        PlaySoundEffect(lose);

                        return;
                    }
                }
            }
        }

        private void PlaySoundEffect(SoundEffect sound)
        {
            if (Storage.IsSoundEnabled)
                sound.Play();
        }

        private XElement SerializeBubbleList(IList<Bubble> enumerable, string name)
        {
            XElement enumerableRootElement = new XElement(name);
            foreach (Bubble b in enumerable)
                enumerableRootElement.Add(b.ToXElement());

            return enumerableRootElement;
        }

        private void DeserializeBubbleList(XElement element, IList<Bubble> list)
        {
            foreach (XElement bubbleElement in element.Elements("bubble"))
                list.Add(DeserializeBubble(bubbleElement));
        }

        private Bubble DeserializeBubble(XElement bubbleElement)
        {
            int number = int.Parse(bubbleElement.Attribute("number").Value);
            int arrayposx = int.Parse(bubbleElement.Attribute("arrayposx").Value);
            int arrayposy = int.Parse(bubbleElement.Attribute("arrayposy").Value);
            float screenposx = float.Parse(bubbleElement.Attribute("screenposx").Value);
            float screenposy = float.Parse(bubbleElement.Attribute("screenposy").Value);
            bool isRootBubble = bool.Parse(bubbleElement.Attribute("isrootbubble").Value);

            Bubble bubble = new Bubble(this, arrayposx, arrayposy, new Vector2(screenposx, screenposy), number);
            bubble.IsRootBubble = isRootBubble;

            return bubble;
        }

        private Vector2 CalculateScoreScreenPosition()
        {
            string level = "" + (_currentLevelNumber + 1);
            float xdiff = font.MeasureString(level).X;
            return new Vector2(_dimensions.ScoresPosition.X - xdiff / 2, _dimensions.ScoresPosition.Y);
        }

        private int GetTotalPlayTime(TimeSpan gameTime)
        {
            TimeSpan playtime = gameTime - _startTime;
            int time = playtime.Seconds + playtime.Minutes * 60;
            return time;
        }
    }
}
